package com.liliudong.template.generator.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import com.liliudong.template.generator.common.GeneratedFile;
import com.liliudong.template.generator.entity.Columns;
import com.liliudong.template.generator.entity.Table;
import com.liliudong.template.generator.service.GeneratorService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ldli5
 * @classname TableServiceImpl
 * @date 2024/7/1-14:24
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {

	private static final HashMap<String, String> TEMPLATE_MAP = new HashMap<>(24) {
		{
			put("entity.java.ftl", "entity/%tablePrefix%/%entityName%.java");
			put("mapper.java.ftl", "mapper/%tablePrefix%/%entityName%Mapper.java");
			put("mapper.xml.ftl", "/resources/%tablePrefix%/%entityName%Mapper.xml");
			put("service.java.ftl", "service/%tablePrefix%/%entityName%Service.java");
			put("serviceImpl.java.ftl", "service/%tablePrefix%/impl/%entityName%ServiceImpl.java");
			put("baseVO.java.ftl", "vo/%tablePrefix%/%entityName%BaseVO.java");
			put("convert.java.ftl", "convert/%tablePrefix%/%entityName%Convert.java");
			put("addDTO.java.ftl", "dto/%tablePrefix%/%entityName%AddDTO.java");
			put("pageDTO.java.ftl", "dto/%tablePrefix%/%entityName%PageDTO.java");
			put("modifyDTO.java.ftl", "dto/%tablePrefix%/%entityName%ModifyDTO.java");
			put("detailVO.java.ftl", "vo/%tablePrefix%/%entityName%DetailVO.java");
			put("pageVO.java.ftl", "vo/%tablePrefix%/%entityName%PageVO.java");
			put("controller.java.ftl", "controller/%tablePrefix%/%entityName%Controller.java");
			put("script.txt.ftl", "%tablePrefix%-%mappingBase%.txt");
			put("api.js.ftl", "apis/%tablePrefix%/%mappingBase%.js");
			put("index.vue.ftl", "views/%tablePrefix%/%mappingBase%/index.vue");
			put("operate-drawer.vue.ftl", "views/%tablePrefix%/%mappingBase%/operate-drawer.vue");
		}
	};

	private static final String ATTACHMENT_FILENAME = "attachment;filename=code-generator-%s.zip";

	private final JdbcTemplate jdbcTemplate;

	private final Configuration configuration;

	@Override
	public List<Table> page(Long page, Long size, String tableName) {
		StringBuilder sqlBuilder = new StringBuilder("""
				SELECT TABLE_NAME AS tableName,
				       TABLE_COMMENT AS tableComment,
				       CREATE_TIME AS createTime
				FROM INFORMATION_SCHEMA.TABLES
				WHERE TABLE_SCHEMA = ?
				""");
		long index = (page - 1) * size;
		String databaseName = getDatabaseName();
		Object[] params;
		if (StrUtil.isNotBlank(tableName)) {
			sqlBuilder.append(" AND TABLE_NAME LIKE CONCAT('%', ?, '%') ");
			params = new Object[]{databaseName, tableName, index, size};
		} else {
			params = new Object[]{databaseName, index, size};
		}
		sqlBuilder.append("""
				order by CREATE_TIME desc
				limit ?, ?
				""");

		List<Map<String, Object>> maps = jdbcTemplate.queryForList(sqlBuilder.toString(), params);
		return Table.fromMaps(maps);
	}

	@Override
	public void generateByTables(HttpServletResponse response, List<String> tables) {
		try {
			@Cleanup ServletOutputStream outputStream = response.getOutputStream();
			List<GeneratedFile> generatedFileList = tables.stream()
					.map(this::generateByTableName)
					.flatMap(List::stream)
					.toList();

			InputStream[] inputStreams = generatedFileList.stream()
					.map(GeneratedFile::getInputStream)
					.toArray(InputStream[]::new);
			String[] paths = generatedFileList.stream()
					.map(GeneratedFile::getPath)
					.toArray(String[]::new);
			ZipUtil.zip(outputStream, paths, inputStreams);

			response.setContentType(ContentType.OCTET_STREAM.getValue());
			response.setHeader(
					Header.CONTENT_DISPOSITION.getValue(),
					String.format(ATTACHMENT_FILENAME, System.currentTimeMillis())
			);
			log.info("------------代码生成成功，共生成{}个文件------------", generatedFileList.size());
		} catch (IOException e) {
			log.error("代码生成失败", e);
		}
	}

	/**
	 * 按表名生成
	 *
	 * @param tableName 表名称
	 */
	private List<GeneratedFile> generateByTableName(String tableName) {
		List<Columns> columns = getColumnsByTableName(tableName);
		boolean entityBase = shouldEntityBase(columns);
		String tablePrefix = getTablePrefix(tableName);
		String tableComment = getTableComment(tableName);
		String username = getSystemUsername();
		String createTime = getCreateTime();
		String entityName = getEntityName(tableName);
		String tableDef = getTableDef(tableName);
		String basePermission = getBasePermission(tableName);
		String mappingBase = getMappingBase(tableName);
		String entityNameLowerFirst = StrUtil.lowerFirst(entityName);
		Map<String, Object> params = MapUtil.<String, Object>builder()
				.put("tableName", tableName)
				.put("createTime", createTime)
				.put("username", username)
				.put("tablePrefix", tablePrefix)
				.put("tableComment", tableComment)
				.put("commonPackage", "com.liliudong.template.common")
				.put("bizPackage", "com.liliudong.template.biz")
				.put("adminPackage", "com.liliudong.template.api.admin")
				.put("entityBase", entityBase)
				.put("entityName", entityName)
				.put("entityNameLowerFirst", entityNameLowerFirst)
				.put("columns", columns)
				.put("basePermission", basePermission)
				.put("mappingBase", mappingBase)
				.put("tableDef", tableDef)
				.build();
		return TEMPLATE_MAP.keySet()
				.stream()
				.map(key -> {
					try {
						@Cleanup InputStream inputStream = generateAndGetInputStream(params, key);
						// String path = String.format(TEMPLATE_MAP.get(key), tablePrefix, entityName);
						String path = TEMPLATE_MAP.get(key)
								.replace("%tablePrefix%", tablePrefix)
								.replace("%entityName%", entityName)
								.replace("%mappingBase%", mappingBase);
						return new GeneratedFile(inputStream, path);
					} catch (IOException e) {
						log.error("生成失败", e);
					} catch (TemplateException e) {
						log.error("模板渲染失败", e);
					}
					return null;
				})
				.filter(Objects::nonNull)
				.toList();
	}

	private String getTableDef(String tableName) {
		return tableName.toUpperCase(Locale.ROOT);
	}

	/**
	 * 生成并获取输入流
	 *
	 * @param params       params
	 * @param templateName 模板名称
	 * @return {@link InputStream }
	 */
	private InputStream generateAndGetInputStream(Map<String, Object> params, String templateName) throws IOException, TemplateException {
		StringWriter stringWriter = new StringWriter();
		Template template = configuration.getTemplate(templateName);
		template.process(params, stringWriter);
		return new ByteArrayInputStream(stringWriter.toString().getBytes(StandardCharsets.UTF_8));
	}

	/**
	 * 获取映射基础
	 *
	 * @param tableName 表名称
	 * @return {@link String }
	 */
	private String getMappingBase(String tableName) {
		return Arrays.stream(tableName.split("_"))
				.skip(1)
				.collect(Collectors.joining("-"));
	}

	/**
	 * 获取基本权限
	 *
	 * @param tableName 表名称
	 * @return {@link String }
	 */
	private String getBasePermission(String tableName) {
		// 表名格式为 sys_user_role 则基础权限标识符为 sys:user:role
		return tableName.replaceAll("_", ":");
	}


	/**
	 * 获取实体名称
	 *
	 * @param tableName 表名称
	 * @return {@link String }
	 */
	private static String getEntityName(String tableName) {
		return StrUtil.upperFirst(StrUtil.toCamelCase(tableName));
	}

	/**
	 * 获取创建时间
	 *
	 * @return {@link String }
	 */
	private String getCreateTime() {
		return LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");
	}

	/**
	 * 获取系统用户名
	 *
	 * @return {@link String }
	 */
	private String getSystemUsername() {
		Map<String, String> map = System.getenv();
		return map.get("USERNAME");
	}

	/**
	 * 获取表注释
	 *
	 * @param tableName 表名称
	 * @return {@link String }
	 */
	private String getTableComment(String tableName) {
		String databaseName = getDatabaseName();
		String sql = """
				SELECT TABLE_COMMENT
				FROM INFORMATION_SCHEMA.TABLES
				WHERE TABLE_SCHEMA = ? and TABLE_NAME = ?
				""";
		return jdbcTemplate.queryForObject(sql, String.class, databaseName, tableName);
	}

	/**
	 * 实体基础
	 *
	 * @param columns 柱
	 * @return boolean
	 */
	private boolean shouldEntityBase(List<Columns> columns) {
		return columns.stream()
				.anyMatch(column -> Objects.equals(column.getColumnName().toLowerCase(), "deleted"));
	}

	/**
	 * 获取表前缀
	 *
	 * @param tableName 表名称
	 * @return {@link String }
	 */
	private String getTablePrefix(String tableName) {
		return StrUtil.subBefore(tableName, "_", false);
	}

	/**
	 * 按表名获取列
	 *
	 * @param tableName 表名称
	 * @return {@link List }<{@link Columns }>
	 */
	private List<Columns> getColumnsByTableName(String tableName) {
		String databaseName = getDatabaseName();
		String sql = """
				SELECT COLUMN_NAME as columnName,
				       COLUMN_TYPE as columnType,
				       COLUMN_COMMENT as columnComment,
				       IS_NULLABLE as isNullable,
				       COLUMN_KEY as columnKey,
				       EXTRA as extra
				FROM INFORMATION_SCHEMA.COLUMNS
				WHERE TABLE_SCHEMA = ?
				  AND TABLE_NAME = ?
				ORDER BY ORDINAL_POSITION
				""";
		List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, databaseName, tableName);
		return Columns.fromMaps(maps);
	}

	/**
	 * 获取数据库名称
	 *
	 * @return {@link String }
	 */
	private String getDatabaseName() {
		String sql = "SELECT DATABASE()";
		return jdbcTemplate.queryForObject(sql, String.class);
	}
}
